//
// Created by 郝羽 on 2018/2/5.
//

#ifndef RTC_XSWITCH2_MIXER_INPUT_HPP
#define RTC_XSWITCH2_MIXER_INPUT_HPP


extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswresample/swresample.h>
#include <libavutil/opt.h>
};

#include <list>
#include <string>

class Input {
    const int channel_layout = AV_CH_LAYOUT_MONO;
    const int sample_rate = 16000;
public:
    int Open(const std::string& filename, const int ptime) {
        AVFormatContext* fmt = nullptr;
        int ret = avformat_open_input(&fmt, filename.c_str(), nullptr, nullptr);
        if (ret < 0) {
            std::clog << "could not open file " << filename << ": " << av_err2str(ret) << std::endl;
            return -1;
        }
        std::clog << "opened file " << filename << std::endl;

        ret = avformat_find_stream_info(fmt, nullptr);
        if (ret < 0) {
            std::clog << "could not find stream info" << std::endl;
            avio_close(fmt->pb);
            avformat_free_context(fmt);
            return -1;
        }

        int audio_stream_id = av_find_best_stream(fmt, AVMEDIA_TYPE_AUDIO, -1, -1, nullptr, 0);
        if (audio_stream_id < 0) {
            std::clog << "file " << filename << " contains no audio stream: " << av_err2str(audio_stream_id) << std::endl;
            avio_close(fmt->pb);
            avformat_free_context(fmt);
            return -1;
        }

        AVStream* stream = fmt->streams[audio_stream_id];
        AVCodec* codec = avcodec_find_decoder(stream->codecpar->codec_id);
        if (!codec) {
            std::clog << "codec " << avcodec_get_name(stream->codecpar->codec_id) << " not supported" << std::endl;
            avio_close(fmt->pb);
            avformat_free_context(fmt);
            return -1;
        }

        AVCodecContext* decoder = avcodec_alloc_context3(codec);
        if (!decoder) {
            std::clog << "could not alloc " << avcodec_get_name(stream->codecpar->codec_id) << " decoder" << std::endl;
            avio_close(fmt->pb);
            avformat_free_context(fmt);
            return -1;
        }

        ret = avcodec_parameters_to_context(decoder, stream->codecpar);
        if (ret < 0) {
            std::clog << "could not pass codec parameters to decoder: " << av_err2str(ret) << std::endl;
            avcodec_free_context(&decoder);
            avio_close(fmt->pb);
            avformat_free_context(fmt);
            return -1;
        }

        AVDictionary* opts = nullptr;
        av_dict_set(&opts, "refcounted_frames", "1", 0);
        ret = avcodec_open2(decoder, codec, &opts);
        if (ret < 0) {
            std::clog << "could not initialize decoder: " << av_err2str(ret) << std::endl;
            avcodec_free_context(&decoder);
            avio_close(fmt->pb);
            avformat_free_context(fmt);
            return -1;
        }

        SwrContext* swr = swr_alloc();
        if (!swr) {
            std::clog << "could not allocate software re-sampler" << std::endl;
            avcodec_free_context(&decoder);
            avio_close(fmt->pb);
            avformat_free_context(fmt);
            return -1;
        }

        // todo: investigate why channel_layout was not set?
        if (stream->codecpar->channel_layout == 0) {
            stream->codecpar->channel_layout = av_get_default_channel_layout(stream->codecpar->channels);
        }

        av_opt_set_channel_layout(swr, "in_channel_layout", stream->codecpar->channel_layout, 0);
        av_opt_set_int(swr, "in_sample_rate", stream->codecpar->sample_rate, 0);
        av_opt_set_sample_fmt(swr, "in_sample_fmt", (AVSampleFormat)stream->codecpar->format, 0);

        av_opt_set_channel_layout(swr, "out_channel_layout", channel_layout, 0);
        av_opt_set_int(swr, "out_sample_rate", sample_rate, 0);
        av_opt_set_sample_fmt(swr, "out_sample_fmt", (AVSampleFormat)AV_SAMPLE_FMT_S16, 0);

        ret = swr_init(swr);
        if (ret < 0) {
            std::clog << "could not initialize software re-sampler: " << av_err2str(ret) << std::endl;
            swr_free(&swr);
            avcodec_free_context(&decoder);
            avio_close(fmt->pb);
            avformat_free_context(fmt);
        }

        AVFrame* frame = av_frame_alloc();
        AVPacket packet;
        while (av_read_frame(fmt, &packet) >= 0) {
            if (packet.stream_index != audio_stream_id) {
                continue;
            }
            ret = avcodec_send_packet(decoder, &packet);
            if (ret < 0 && ret != AVERROR(EAGAIN)) {
                swr_free(&swr);
                avcodec_free_context(&decoder);
                avio_close(fmt->pb);
                avformat_free_context(fmt);
                return -1;
            }

            while (avcodec_receive_frame(decoder, frame) == 0) {
                AVFrame* out = av_frame_alloc();
                out->nb_samples = sample_rate / 1000 * ptime;
                out->sample_rate = sample_rate;
                out->format = AV_SAMPLE_FMT_S16;
                out->channels = 1;
                out->channel_layout = AV_CH_LAYOUT_MONO;
                ret = av_frame_get_buffer(out, 0);
                if (ret < 0) {
                    std::clog << "could not allocate frame for software re-sampler: " << av_err2str(ret) << std::endl;
                    avcodec_free_context(&decoder);
                    avio_close(fmt->pb);
                    avformat_free_context(fmt);
                    swr_free(&swr);
                }
                std::clog << "in samples " << frame->nb_samples << std::endl;

                int out_samples = ptime * sample_rate / 1000;
                int n = swr_convert(swr, out->data, out_samples, (const uint8_t**)frame->data, frame->nb_samples);
                std::clog << "out samples " << n << std::endl;
                out->nb_samples = n;
                av_samples_get_buffer_size(&out->linesize[0], av_get_channel_layout_nb_channels(channel_layout), n, AV_SAMPLE_FMT_S16, 1);

                frames_.emplace_back(out);

                // todo: ensure out 160 samples
                while (swr_get_delay(swr, 1000) > 2 * ptime) {
                    AVFrame* out = av_frame_alloc();
                    out->nb_samples = sample_rate / 1000 * ptime;
                    out->sample_rate = sample_rate;
                    out->format = AV_SAMPLE_FMT_S16;
                    out->channels = 1;
                    out->channel_layout = AV_CH_LAYOUT_MONO;
                    ret = av_frame_get_buffer(out, 0);
                    if (ret < 0) {
                        std::clog << "could not allocate frame for software re-sampler: " << av_err2str(ret) << std::endl;
                        avcodec_free_context(&decoder);
                        avio_close(fmt->pb);
                        avformat_free_context(fmt);
                        swr_free(&swr);
                    }
                    int n = swr_convert(swr, out->data, out_samples, nullptr, 0);
                    std::clog << "out samples " << n << std::endl;
                    out->nb_samples = n;
                    av_samples_get_buffer_size(&out->linesize[0], av_get_channel_layout_nb_channels(channel_layout), n, AV_SAMPLE_FMT_S16, 1);
                    frames_.emplace_back(out);
                }
            }
        }

        avcodec_free_context(&decoder);
        avio_close(fmt->pb);
        avformat_free_context(fmt);
        swr_free(&swr);

        next_frame_ = frames_.cbegin();
        return 0;
    }

    AVFrame* Read() {
        if (next_frame_ == frames_.end()) {
            return nullptr;
        }
        return *(next_frame_++);
    }

    void Close() {
        for (auto f : frames_) {
            av_frame_free(&f);
        }
        frames_.clear();
    }

private:
    std::string filename_;
    std::list<AVFrame*> frames_;
    std::list<AVFrame*>::const_iterator next_frame_;
};

#endif //RTC_XSWITCH2_MIXER_INPUT_HPP
